场景:基于Spring Boot使用Java调用http请求的6种方式。服务端发布一个POST请求和2个GET请求。使用6种方式实现的客户端都调用服务端发布的这3个方法。可以直观感受和比对6种http请求的客户端。
项目实体类属性不存入数据库排除方式
- @Transient
该注解只适用于hibernate框架,在实体类(pojo)属性上使用、表示数据库表中没有这个字段就忽略;
- @TableField
该注解只适用于mybatis-plus框架:
@TableField(exist = false):表示该属性不为数据库表字段,但又是必须使用的。
@TableField(exist = true):表示该属性为数据库表字段。
阿里EasyExcel大数据导入用法
pom文件依赖
1 2 3 4 5 6
| <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.0.5</version> </dependency>
|
导入(读)数据–第一步创建一个监听器ExcelDataListener.java继承AnalysisEventListener类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
|
package com.xiaogui.log.utils;
import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.metadata.Cell; import com.alibaba.excel.read.metadata.holder.ReadRowHolder; import com.alibaba.excel.support.ExcelTypeEnum; import com.alibaba.fastjson.JSON; import com.xiaogui.log.mapper.LogMapper; import com.xiaogui.log.vo.resp.RespLogInfo; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.*;
@Component @Slf4j public class ExcelDataListener extends AnalysisEventListener<RespLogInfo> {
private static final int BATCH_COUNT = 10; List<与Excel表头对应的Entity> list = new ArrayList<>();
private final RespLogInfoService respLogInfoService;
public ExcelDataListener(RespLogInfoService respLogInfoService) { this.respLogInfoService = respLogInfoService; }
@Override public void invoke(与Excel表头对应的Entity data, AnalysisContext context) { log.info("解析到一条数据:{}", JSON.toJSONString(data)); list.add(data); if (list.size() >= BATCH_COUNT) { respLogInfoService.saveOrUpdateBatch(list); list.clear(); } }
@Override public void doAfterAllAnalysed(AnalysisContext context) { respLogInfoService.saveOrUpdateBatch(list); log.info("所有数据解析完成!"); } }
|
实体类
1 2 3
| public class RespLogInfo implements Serializable { @ExcelProperty(index = 0) private String type;
|
用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package easyExcel;
import com.alibaba.excel.EasyExcel; import com.easyexcel.listener.EasyExcelOrderListener; import com.easyexcel.pojo.ExcelOrder; import org.junit.Test;
public class ExcelReadTest { @Test public void excelRead(){ EasyExcel.read(file.getInputStream, RespLogInfo.class,new FileListener(this)).sheet().doRead(); } }
|
两个实体类互相copy
1
| BeanUtils.copyProperties(被copyEntity,targetEntity);
|
实体Entity转Map
1
| Map<String,String> oMapper = new ObjectMapper().convertValue(clerk, HashMap.class);
|
常量的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static final String MONDAY = "test"; public static final Map map = new HashMap(); static { map.put("key1", "value1"); map.put("key2", "value2"); } public static final Map<String, String> map = new HashMap<>() { { put("key1", "value1"); put("key2", "value2"); } }; public static final List<String> list = Arrays.asList("88","99","100");
|
SpringBoot使用RequestBodyAdvice进行统一参数处理
请求处理====在实际项目中 , 往往需要对请求参数做一些统一的操作 , 例如参数的过滤 , 字符的编码 , 第三方的解密等等 , Spring提供了RequestBodyAdvice一个全局的解决方案 , 免去了我们在Controller处理的繁琐 .RequestBodyAdvice仅对使用了@RqestBody注解的生效 , 因为它原理上还是AOP , 所以GET方法是不会操作的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| package com.xbz.common.web; import org.springframework.core.MethodParameter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Type;
@Component @ControllerAdvice(assignableTypes = {TestController.class}) public class GlobalRequestBodyAdvice implements RequestBodyAdvice { private final Logger log = LoggerFactory.getLogger(getClass());
@Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return true; }
@Override public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { return inputMessage; }
@Override public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { log.info("第三方请求加密数据:"+body) String aaa = body; return aaa; }
@Override public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return body; } }
|
响应处理=====实现ResponseBodyAdvice接口,其实是对加了@RestController(也就是@Controller+@ResponseBody)注解的处理器将要返回的值进行增强处理。
其实也就是采用了AOP的思想,对返回值进行一次修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @ControllerAdvice(assignableTypes = {TestController.class}) @Component public class MyResponseBodyAdvice implements ResponseBodyAdvice { private final Logger log = LoggerFactory.getLogger(getClass());
@Override public boolean supports(MethodParameter returnType, Class converterType) { return true; }
@Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { log.info("响应第三方数据加密处理开始:"+body) String sendXml = body; return sendXml; } }
|
vue中将后台返回的数字转换成对应的文字
7.1、对于列表循环el-table-column采用如下方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 第一种方案 <el-table-column prop="status" :show-overflow-tooltip="true" label="状态" width="60" :formatter="statusFormatter"> </el-table-column>ji
methods:{ statusFormatter(row, column){ } } 第二种方案 <el-table-column prop="type" label="类型" align="center"> <template v-slot="{ row }"> <span v-show="row.type == 1">普通用户</span> <span v-show="row.type == 2">管理员</span> <span v-show="row.type == 3">项目经理</span> </template> </el-table-column>
|
7.2、对于详情或者修改页面采用如下方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 在Vue中,created和mounted的区别是created用来初始化属性值,mounted用来操作属性值。下面小编举例讲解Vue中created和mounted的区别是什么。 created是用来初始化页面的值的 mounted是修改页面的值的 created() { console.log(this.dataMsg); console.log(this.propMsg); }, mounted() { console.log(this.dataMsg); this.submit() console.log(this.propMsg); } 上面方法mounted有可能只触发一次,就是说详情页面关闭后再打开mounted中的方法不会再次执行。 完美方案v-model绑定计算属性: <el-form-item> <span slot="label"> <i class="el-icon-edit"></i> 活动名称 </span> <el-input v-model="formatValue"></el-input> </el-form-item> 计算属性定义 computed: { formatValue() { if(this.inputvalue === '0'){ return "中国"; } } },
|
实体类属性copy
1
| BeanUtils.copyProperties(source,target);
|
el-table-column宽度自适应
1 2 3 4 5 6 7 8
| 如下这种方式会把每列宽度设置为10px <el-table-column prop="sort" label="Sort" width="10%"> 如下是百分比的写法 <el-table-column prop="sort" label="Sort" min-width="50%"> <el-table-column prop="sort" label="Sort" min-width="50%">
|
el-table表格中单元格内容过多显示省略号
1 2 3 4 5 6 7 8 9 10
| el-table表格中内容超出单元格的宽度会自动换行,会使整个表格看起来显得不太美观, 此时可以使用el-table-column 自带的 show-overflow-tooltip="true" 属性来设置,可以使超出单元格宽度的内容变成省略号, 而且鼠标放上去会提示单元格中原本有的全部的内容 <el-table-column prop="address" label="地址" show-overflow-tooltip="true" min-width="100">
</el-table-column>
|
mybatis-plus的LambdaQueryWrapper自定义sql用法
1 2 3 4 5 6 7 8 9 10 11
| # 如果排序的字段需要先转换类型呢 # 那么就需要 sql自由拼接方法 (wrapper.apply) @Override public List<SysRoleEntity> selectListByTypeCode(String typeCode) { LambdaQueryWrapper<SysRoleEntity> wrapper = new LambdaQueryWrapper<SysRoleEntity>() .eq(SysRoleEntity::getTypeCode,typeCode)
.apply("ORDER BY TO_NUMBER(SEQUENCEVALUE) ASC"); } 或者 apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2020-10-08")
|
mybatis-plus的@Select注解用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Select("select BLOCK_ID,BLOCK_NAME,PARENT_BLOCK_ID,BLOCK_LEVEL,ORDER_NUM from XY_DIC_BLOCK_T where block_level=1 " ) public List<Block> sqlMany();
@Select("select BLOCK_ID,BLOCK_NAME,PARENT_BLOCK_ID,BLOCK_LEVEL,ORDER_NUM from XY_DIC_BLOCK_T where block_level=#{level}" ) public List<Block> sqlManyParm(String level);
@Select("<script> select BLOCK_ID,BLOCK_NAME,PARENT_BLOCK_ID,BLOCK_LEVEL,ORDER_NUM from XY_DIC_BLOCK_T where 1=1 " + "<if test='level != null'>" + " and block_level=#{level} " + "</if>" + "</script>") public List<Block> sqlManyParmNull(String level);
@Select("<script> select BLOCK_ID,BLOCK_NAME,PARENT_BLOCK_ID,BLOCK_LEVEL,ORDER_NUM from XY_DIC_BLOCK_T where 1=1 " + "<if test='item.blockLevel != null'>" + " and block_level=#{item.blockLevel} " + "</if>" + "</script>") public List<Block> sqlManyObject(@Param("item") Block block);
|
vue调用子组件作为弹窗时只执行一次created问题
解决办法:用v-if将子组件包裹起来,因为v-if=false时可以将子组件销毁掉,再次调用时重新渲染
补充知识:vue如何每次打开子组件弹窗都进行初始化 :visible.sync 与 v-if同时使用即可
Java字符串前后补零的几种方法
数字类型前补 0
1
| String.format("%08d", 123);
|
字符串类型前补 0
1 2
| String.format("%8s", "abc").replace(" ", "0");
|
也可以先在前面补 8 位的 0,再截取:
1 2 3
| String str = "00000000" + "abc"; str.substring(str.length() - 8);
|
后补 0
对于后补 0,都可以使用一种方式来做,就是在后面加上 00000…,之后截取:
1 2
| (123 + "00000000").substring(0, 8);
|
这种方式通用任何类型
springboot集成https
1 2 3
| 生成证书 keytool -genkey -alias tomcat -dname "CN=Andy,OU=kfit,O=kfit,L=HaiDian,ST=BeiJing,C=CN" -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 365 说明:"CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名称,ST=州或省份名称,C=单位的两字母国家代码"
|
输入后会提示输入密码,这个密码在下面配置文件有用到。
生成后,在家目录找到证书文件,复制到SpringBoot应用的src/main/resources下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| application.yml配置如下信息 server: ssl:
key-store: classpath:keystore.p12
key-alias: tomcat enabled: true key-store-type: PKCS12
key-store-password: 123456
port: 443 说明:端口443可以改成任意值
|
此时启动SpringBoot应用,发现可以通过https访问了====快去打开浏览器访问试试😉。
想要http同时访问就看下面,否则跳过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 在yml配置文件中,添加http端口号定义 server http: port: 8888 创建配置类 @Value("${server.http.port}") private Integer httpPort;
@Bean public ServletWebServerFactory servletContainer(){ final Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); connector.setPort(httpPort); final TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addAdditionalTomcatConnectors(connector); return tomcat; } 启动项目时,我们会发现如下日志,Tomcat绑定了两个端口号,其中https绑定在8080,http绑定在8888。 恭喜你通关
|
项目防刷控制(自定义注解)
创建一个自定义注解
1 2 3 4 5 6 7 8 9 10 11
| package com.example.annotation; import java.lang.annotation.*;
@Documented @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AccessLimit { int seconds() default 1; int maxCount() default 1; }
|
2.创建一个拦截器 (用于拦截请求,更新当前用户访问的次数,如果访问受限,则返回超时的状态码)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| package com.example.interceptor;
import com.example.annotation.AccessLimit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.OutputStream; import java.util.concurrent.TimeUnit;
@Component public class FangshuaInterceptor extends HandlerInterceptorAdapter { @Resource RedisTemplate<String, Object> redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handler1 = (HandlerMethod) handler;
AccessLimit accessLimit = handler1.getMethodAnnotation(AccessLimit.class); if (accessLimit != null) {
if (isLimit(request, accessLimit)) { render(response, "{\"code\":\"30001\",\"message\":\"请求过快\"}"); return false; } } } return true; }
public boolean isLimit(HttpServletRequest request, AccessLimit accessLimit) { String limitKey = request.getServletPath() + request.getSession().getId(); Integer redisCount = (Integer) redisTemplate.opsForValue().get(limitKey); if (redisCount == null) { redisTemplate.opsForValue().set(limitKey, 1, accessLimit.seconds(), TimeUnit.SECONDS); System.out.println("写入redis --"); } else { System.out.println("intValue-->" + redisCount.intValue()); if (redisCount.intValue() >= accessLimit.maxCount()) { return true; } redisTemplate.opsForValue().increment(limitKey); } return false; }
private void render(HttpServletResponse response, String cm) throws Exception { response.setContentType("application/json;charset=UTF-8"); OutputStream out = response.getOutputStream(); out.write(cm.getBytes("UTF-8")); out.flush(); out.close(); } }
|
3.注册拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| package com.example.interceptor;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.io.Serializable;
@Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Autowired private FangshuaInterceptor interceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(interceptor); }
@Bean public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) {
RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory); return redisTemplate; } }
|
4.OK , 下面我们就可以在需要进行现在访问次数的controller中的方法使用该注解了
1 2 3 4 5 6 7 8 9 10
| @RestController @RequestMapping("/test") public class Test { @GetMapping("/test1")
@AccessLimit(seconds = 20, maxCount = 2) public String test1() { return "我是test1"; } }
|
string转json
1 2
| JSONObject json=new JSONObject(“”); String key = json.getString(key);
|
SpringBoot项目业务操作日志记录
1、依赖配置
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
|
2、表结构设计
1 2 3 4 5 6 7 8 9 10
| create table if not exists bus_log( id bigint auto_increment comment '自增id' primary key, bus_name varchar(100) null comment '业务名称', bus_descrip varchar(255) null comment '业务操作描述', oper_person varchar(100) null comment '操作人', oper_time datetime null comment '操作时间', ip_from varchar(50) null comment '操作来源ip', param_file varchar(255) null comment '操作参数报文文件' )comment '业务操作日志' default charset ='utf8';
|
3、定义业务日志注解@BusLog,可以作用在控制器或其他业务类上,用于描述当前类的功能;也可以用于方法上,用于描述当前方法的作用
1 2 3 4 5 6 7 8 9
| @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface BusLog {
String name() default "";
String descrip() default ""; }
|
4、把业务操作日志注解BusLog标记在PersonController类和方法上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @RestController @Slf4j @BusLog(name = "人员管理") @RequestMapping("/person") public class PersonController { @Autowired private IPersonService personService; private Integer maxCount=100;
@PostMapping @NeedEncrypt @BusLog(descrip = "添加单条人员信息") public Person add(@RequestBody Person person) { Person result = this.personService.registe(person); log.info("//增加person执行完成"); return result; } }
|
5、编写切面类BusLogAop,并使用@BusLog定义切入点,在环绕通知内执行过目标方法后,获取目标类、目标方法上的业务日志注解上的功能名称和功能描述, 把方法的参数报文写入到文件中,最后保存业务操作日志信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| @Component @Aspect @Slf4j public class BusLogAop implements Ordered { @Autowired private BusLogDao busLogDao;
@Pointcut(value = "@annotation(com.fanfu.anno.BusLog)") public void pointcut() { } @Around("pointcut()") public Object around(ProceedingJoinPoint proceedingJoinPoint) { log.info("----BusAop 环绕通知 start"); Object result = null; try { result = proceedingJoinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } Object target = proceedingJoinPoint.getTarget(); Object[] args = proceedingJoinPoint.getArgs(); MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature(); BusLog anno1 = target.getClass().getAnnotation(BusLog.class); BusLog anno2 = signature.getMethod().getAnnotation(BusLog.class); BusLogBean busLogBean = new BusLogBean(); String logName = anno1.name(); String logDescrip = anno2.descrip(); busLogBean.setBusName(logName); busLogBean.setBusDescrip(logDescrip); busLogBean.setOperPerson("fanfu"); busLogBean.setOperTime(new Date()); JsonMapper jsonMapper = new JsonMapper(); String json = null; try { json = jsonMapper.writeValueAsString(args); } catch (JsonProcessingException e) { e.printStackTrace(); } OutputStream outputStream = null; try { String paramFilePath = System.getProperty("user.dir") + File.separator + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN) + ".log"; outputStream = new FileOutputStream(paramFilePath); outputStream.write(json.getBytes(StandardCharsets.UTF_8)); busLogBean.setParamFile(paramFilePath); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (outputStream != null) { try { outputStream.flush(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } this.busLogDao.insert(busLogBean); log.info("----BusAop 环绕通知 end"); return result; } @Override public int getOrder() { return 1; } }
|
jackson返回前端的字符串中引号被自动加上反斜杆
- 对象中有字符串是返回的对象中有反斜杆
解决方法:
使用JsonNode1 2 3 4 5 6 7 8 9
| class DtoNew { JsonNode data; } ObjectMapper mapper = new ObjectMapper(); try { dtoNew.data = mapper.readTree(dto.data )); } catch (IOException e) { e.printStackTrace(); }
|
实际使用方法1 2 3 4 5 6 7 8 9 10 11 12 13
| @JsonIgnore private String preserveList;
private JsonNode preserve_list;
public JsonNode getPreserve_list(){ try { return new ObjectMapper().readTree(this getPreserveList()); }catch (JsonProcessingException e) { e.printStackTrace(); return null; } }
|
- 对象中字段转换json输出名字
1 2
| @JsonProperty("door_no") private String doorNo;
|
- 忽略字段的输出
1 2
| @JsonIgnore private String doorNo;
|
结尾。